package com.aiwiown.face.internal.mapping;

import com.aiwiown.face.ApiConstants;
import com.aiwiown.face.ApiException;
import com.aiwiown.face.ApiResponse;
import com.aiwiown.face.internal.util.StringUtils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 转换工具类。
 *
 * @author carver.gu
 * @since 1.0, Apr 11, 2010
 */
public class Converters {

    private static Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
    // 是否对JSON返回的数据类型进行校验，默认不校验。给内部测试JSON返回时用的开关。
    //规则：返回的"基本"类型只有String,Long,Boolean,Date,采取严格校验方式，如果类型不匹配，报错
    public static boolean isCheckJsonType = false;

    private static final Set<String> baseFields = new HashSet<String>();

    private static final Set<String> excludeFields = new HashSet<String>();

    /**
     * 被子类覆盖的属性
     */
    private static final Set<String> overideFields = new HashSet<String>();

    static {
        baseFields.add("error");
        baseFields.add("body");
        baseFields.add("params");
        baseFields.add("timeUsed");
        baseFields.add("requestId");
        baseFields.add("errorMessage");
        baseFields.add("paramsByteArr");
    }

    static {
        /**
         * lite gettoken 和app-gettbiztoken不是同期的产品，所以error这块略有不同。
         * lite gettoken 错误信息会返回到error上
         * app-gettbiztoken 错误信息会返回到 error_message
         * 不需要过滤 errorMessage ,否则会导致解析错误
         */
//        excludeFields.add("errorMessage");
        excludeFields.add("code");
        excludeFields.add("message");

    }

    static {
        overideFields.add("code");
        overideFields.add("msg");
    }

    private Converters() {
    }

    /**
     * 使用指定 的读取器去转换字符串为对象。
     *
     * @param <T>    领域泛型
     * @param clazz  领域类型
     * @param reader 读取器
     * @return 领域对象
     * @throws ApiException
     */
    public static <T> T convert(Class<T> clazz, Reader reader) throws ApiException {
        T rsp = null;
        try {
            rsp = clazz.newInstance();
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
            boolean isResponseClazz = ApiResponse.class.isAssignableFrom(clazz);
            for (PropertyDescriptor pd : pds) {
                Method writeMethod = pd.getWriteMethod();
                if (writeMethod == null) {
                    continue;
                }
                String itemName = pd.getName();
                String listName = null;
                // 之前errorCode的属性要剔除掉
                if (isResponseClazz && excludeFields.contains(itemName)) {
                    continue;
                }
                List<ApiMethod> faceFieldMethods = new ArrayList<ApiMethod>();
                if (baseFields.contains(itemName) && isResponseClazz) {
                    Field field = ApiResponse.class.getDeclaredField(itemName);
                    ApiMethod faceFieldMethod = new ApiMethod();
                    faceFieldMethod.setField(field);
                    // writeMethod属于父类，则直接使用
                    if (writeMethod.getDeclaringClass().getName().contains("ApiResponse")) {
                        faceFieldMethod.setMethod(writeMethod);
                    } else {
                        // 否则从父类再取一次
                        writeMethod = tryGetSetMethod(ApiResponse.class, field,
                                writeMethod.getName());
                        if (writeMethod == null) {
                            continue;
                        }
                        faceFieldMethod.setMethod(writeMethod);
                    }
                    faceFieldMethods.add(faceFieldMethod);
                    // 如果被子类覆盖的，则从尝试从子类中获取
                    if (overideFields.contains(itemName)) {
                        field = tryGetFieldWithoutExp(clazz, itemName);
                        // 属性存在则需要重新从子类获取访问方法
                        if (field != null) {
                            writeMethod = tryGetSetMethod(clazz, field, writeMethod.getName());
                            if (writeMethod == null) {
                                continue;
                            }
                            faceFieldMethod = new ApiMethod();
                            faceFieldMethod.setField(field);
                            faceFieldMethod.setMethod(writeMethod);
                            faceFieldMethods.add(faceFieldMethod);
                        }
                    }
                } else {
                    Field field = clazz.getDeclaredField(itemName);
                    ApiMethod faceFieldMethod = new ApiMethod();
                    faceFieldMethod.setField(field);
                    faceFieldMethod.setMethod(writeMethod);
                    faceFieldMethods.add(faceFieldMethod);
                }
                // 迭代设置属性
                for (ApiMethod faceFieldMethod : faceFieldMethods) {
                    Field field = faceFieldMethod.getField();
                    Method method = faceFieldMethod.getMethod();
                    ApiField jsonField = field.getAnnotation(ApiField.class);
                    if (jsonField != null) {
                        itemName = jsonField.value();
                    }
                    ApiListField jsonListField = field.getAnnotation(ApiListField.class);
                    if (jsonListField != null) {
                        listName = jsonListField.value();
                    }
                    if (!reader.hasReturnField(itemName)) {
                        if (listName == null || !reader.hasReturnField(listName)) {
                            continue; // ignore non-return field
                        }
                    }
                    Class<?> typeClass = field.getType();
                    if (String.class.isAssignableFrom(typeClass)) {
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof String) {
                            method.invoke(rsp, value.toString());
                        } else {
                            if (isCheckJsonType && value != null) {
                                throw new ApiException(itemName + " is not a String");
                            }
                            if (value != null) {
                                method.invoke(rsp, value.toString());
                            } else {
                                method.invoke(rsp, "");
                            }
                        }
                    } else if (Long.class.isAssignableFrom(typeClass)) {
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof Long) {
                            method.invoke(rsp, (Long) value);
                        } else {
                            if (isCheckJsonType && value != null) {
                                throw new ApiException(itemName + " is not a Number(Long)");
                            }
                            if (StringUtils.isNumeric(value)) {
                                method.invoke(rsp, Long.valueOf(value.toString()));
                            }
                        }
                    } else if (Integer.class.isAssignableFrom(typeClass)) {
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof Integer) {
                            method.invoke(rsp, (Integer) value);
                        } else {
                            if (isCheckJsonType && value != null) {
                                throw new ApiException(itemName + " is not a Number(Integer)");
                            }
                            if (StringUtils.isNumeric(value)) {
                                method.invoke(rsp, Integer.valueOf(value.toString()));
                            }
                        }
                    } else if (Boolean.class.isAssignableFrom(typeClass)) {
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof Boolean) {
                            method.invoke(rsp, (Boolean) value);
                        } else {
                            if (isCheckJsonType && value != null) {
                                throw new ApiException(itemName + " is not a Boolean");
                            }
                            if (value != null) {
                                method.invoke(rsp, Boolean.valueOf(value.toString()));
                            }
                        }
                    } else if (Double.class.isAssignableFrom(typeClass)) {
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof Double) {
                            method.invoke(rsp, (Double) value);
                        } else {
                            if (isCheckJsonType && value != null) {
                                throw new ApiException(itemName + " is not a Double");
                            }
                        }
                    } else if (Number.class.isAssignableFrom(typeClass)) {
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof Number) {
                            if (BigDecimal.class.isAssignableFrom(typeClass)) {
                                method.invoke(rsp, new BigDecimal(String.valueOf(value)));
                            } else {
                                method.invoke(rsp, (Number) value);
                            }


                        } else {
                            if (isCheckJsonType && value != null) {
                                throw new ApiException(itemName + " is not a Number");
                            }
                        }
                    } else if (Date.class.isAssignableFrom(typeClass)) {
                        DateFormat format = new SimpleDateFormat(ApiConstants.DATE_TIME_FORMAT);
                        format.setTimeZone(TimeZone.getTimeZone(ApiConstants.DATE_TIMEZONE));
                        Object value = reader.getPrimitiveObject(itemName);
                        if (value instanceof String) {
                            method.invoke(rsp, format.parse(value.toString()));
                        }
                    } else if (List.class.isAssignableFrom(typeClass)) {
                        Type fieldType = field.getGenericType();
                        if (fieldType instanceof ParameterizedType) {
                            ParameterizedType paramType = (ParameterizedType) fieldType;
                            Type[] genericTypes = paramType.getActualTypeArguments();
                            if (genericTypes != null && genericTypes.length > 0) {
                                if (genericTypes[0] instanceof Class<?>) {
                                    Class<?> subType = (Class<?>) genericTypes[0];
                                    List<?> listObjs = reader.getListObjects(listName, itemName,
                                            subType);
                                    if (listObjs != null) {
                                        method.invoke(rsp, listObjs);
                                    }
                                }
                            }
                        }
                    } else {
                        Object obj = reader.getObject(itemName, typeClass);
                        if (obj != null) {
                            method.invoke(rsp, obj);
                        }
                    }
                }

            }
        } catch (Exception e) {
            throw new ApiException(e);
        }

        return rsp;
    }

    /**
     * 尝试获取属性
     * <p>
     * 不会抛出异常，不存在则返回null
     *
     * @param clazz
     * @param itemName
     * @return
     */
    private static Field tryGetFieldWithoutExp(Class<?> clazz, String itemName) {

        try {

            return clazz.getDeclaredField(itemName);

        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取属性设置属性
     *
     * @param clazz
     * @param field
     * @return
     */
    private static <T> Method tryGetSetMethod(Class<T> clazz, Field field, String methodName) {

        try {
            return clazz.getDeclaredMethod(methodName, field.getType());
        } catch (Exception e) {

            return null;
        }

    }

    /**
     * unicode编码转换为汉字
     * 注:此方法会丢失转义符 \ ,导致json对象参数转换会抛出异常错误
     *
     * @param unicodeStr 待转化的编码
     * @return 返回转化后的汉子
     */
    @Deprecated
    public static String UnicodeToCN(String unicodeStr) {

        Matcher matcher = pattern.matcher(unicodeStr);
        char ch;
        while (matcher.find()) {
            //group
            String group = matcher.group(2);
            //ch:'李四'
            ch = (char) Integer.parseInt(group, 16);
            //group1
            String group1 = matcher.group(1);
            unicodeStr = unicodeStr.replace(group1, ch + "");
        }
        return unicodeStr.replace("\\", "").trim();
    }

    /**
     * Unicode字符串转为普通字符串<br>
     * Unicode字符串的表现方式为：\\uXXXX
     * <p>
     * 例:
     * 1."aaa123\\u4e2d\\u6587" 转化后为 aaa123中文
     * 2."{\"\\u6587\": 0, \"error_message\": \"AUTHORIZATION_ERROR: \\\"Denied.\\\"\", \"request_id\": \"1589611407,4619d1e4-ecb3-47cb-bbfb-3b15e03d127f\"}";
     * 转化后为
     * "{\"文\": 0, \"error_message\": \"AUTHORIZATION_ERROR: \\\"Denied.\\\"\", \"request_id\": \"1589611407,4619d1e4-ecb3-47cb-bbfb-3b15e03d127f\"}"
     *
     * @param unicodeStr Unicode字符串
     * @return 普通字符串
     */
    public static String unicodeToString(String unicodeStr) {
        if (StringUtils.isEmpty(unicodeStr)) {
            return unicodeStr;
        }

        final int len = unicodeStr.length();
        StringBuilder sb = new StringBuilder(len);
        int i;
        int pos = 0;

        while ((i = unicodeStr.indexOf("\\u", pos)) != -1) {
            //写入Unicode符之前的部分
            sb.append(unicodeStr, pos, i);
            pos = i;
            if (i + 5 < len) {
                char c;
                try {
                    c = (char) Integer.parseInt(unicodeStr.substring(i + 2, i + 6), 16);
                    sb.append(c);
                    //跳过整个Unicode符
                    pos = i + 6;
                } catch (NumberFormatException e) {
                    //非法Unicode符，跳过
                    //写入"\\u"
                    sb.append(unicodeStr, pos, i + 2);
                    pos = i + 2;
                }
            } else {
                //非Unicode符，结束
                break;
            }
        }

        if (pos < len) {
            sb.append(unicodeStr, pos, len);
        }
        return sb.toString();
    }
}
